<?php
/**
 * https://gitee.com/Mao02
 * http://www.mao02.win/
 * jay_fun 410136330@qq.com
 * Date: 2018/4/2 0002
 * Time: 上午 0:00
 */

namespace app\common\service;

use think\Db;
use think\facade\Config;
use think\facade\Env;

/**
 * 项目文件
 * Class App
 * @package jay\Surport
 */
class Node {

	/**
	 * 模块
	 * @param $path
	 * @return \Generator
	 */
	private function module($path) {
		$d = dir($path);
		while (false !== $dir = $d->read()){
			if ($dir === '.' || $dir === '..'){
				continue;
			}
			if (is_dir($path.DIRECTORY_SEPARATOR.$dir)){
				yield $dir;
			}
		}
	}

	/**
	 * 控制器
	 * @param $path
	 * @return \Generator
	 */
	private function controller($path) {
		$d = dir($path);
		while (false !== $file = $d->read()){
			if ($file === '.' || $file === '..'){
				continue;
			}
			if (is_file($path.DIRECTORY_SEPARATOR.$file)){
				yield str_replace('.php','',$file);
			}
		}
	}

    /**
     * 刷新数据库节点数据
     * @return array
     * @throws \ReflectionException
     * @throws \think\Exception
     * @throws \think\exception\PDOException
     */
	public function reload() {
        $node = $this->getFileNodes();
		$ignore = ['common'];
		foreach ($node as $key => $value){
			foreach ($ignore as $item){
				if (0 === strpos($value['path'],$item)){
					unset($node[$key]);
					continue 2;
				}
			}
			$id = Db::name('SystemAuthNode')->where('path',$value['path'])->value('id');
			if ($id){
				Db::name('SystemAuthNode')->where('path',$value['path'])->update($value);
			}else{
				Db::name('SystemAuthNode')->insert($value);
			}
		}

        $list = Db::name('SystemAuthNode')->column('id', 'path');
        $ids = [];
        foreach ($node as $item) {
            if (isset($list[$item['path']])) {
                $ids[] = $list[$item['path']];
            }
        }
        Db::name('SystemAuthNode')->whereNotIn('id', $ids)->delete();
	}

    /**
     * 扫描文件系统 获取全部节点
     * @throws \ReflectionException
     */
    public function getFileNodes()
    {
        $app_path = Env::get('app_path');
        $node = [];
        foreach ($this->module($app_path) as $module){
            if (!in_array($module, Config::get('app.admin_module'))){
                continue;
            }
            $node[] = [
                'path'=>$module,
                'title'=>$module,
                'level'=>1
            ];
            foreach ($this->controller($app_path.$module.DIRECTORY_SEPARATOR.'controller') as $controller){
                $class = Env::get('app_namespace').'\\'.$module.'\\'.'controller'.'\\'.$controller;
                $instance = new \ReflectionClass($class);
                $node[] = [
                    'path' => "{$module}/{$controller}",
                    'title'=>$this->getAnnotation('title',$instance->getDocComment())?:$controller,
                    'level'=>2
                ];
                $parentMethods = [];
                if ($parent = $instance->getParentClass()){
                    foreach ($parent->getMethods() as $m){
                        $parentMethods[] = $m->getName();
                    }
                }
                $methods = $instance->getMethods();
                foreach ($methods as $method){
                    $method_name = $method->getName();
                    // 重写父类的方法和下划线开头的方法会忽略
                    if (in_array($method_name ,$parentMethods) || 0 === strpos($method_name,'_')){
                        continue;
                    }
                    $comment = $method->getDocComment();
                    $auth = $this->getAnnotation('auth',$comment);
                    $node[] = [
                        'path' => "{$module}/{$controller}/{$method_name}",
                        'title' => $this->getAnnotation('title',$comment)?:$method_name,
                        'auth' => (false === $auth)?'2':$auth,
                        'level'=>3
                    ];
                }
            }
        }
        return $node;
	}

    /**
     * 从数据库中获取节点缓存
     * @return array|\PDOStatement|string|\think\Collection
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function getNodesData()
    {

        $list = Db::name('SystemAuthNode')->select();
        usort($list, function($x, $y) {
            return strcasecmp($x['path'],$y['path']);
        });
        return $list;
    }

    /**
     * 获取注解
     * @param $flag
     * @param $comment
     * @return bool
     */
    public function getAnnotation($flag,$comment) {
        preg_match("/@{$flag}\s*([^\s]*)/i",$comment,$matches);
        return $matches[1]??false;
    }
}